Skip to content

Ask model to try again if it produced a response without text or tool call parts (e.g. only thinking) #2469

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: main
Choose a base branch
from

Conversation

ethanabrooks
Copy link

@ethanabrooks ethanabrooks commented Aug 7, 2025

Fixes #2480

@DouweM
Copy link
Collaborator

DouweM commented Aug 7, 2025

@ethanabrooks Thanks, which models have you seen output only thinking with no text or tool calls to follow it?

@DouweM DouweM self-assigned this Aug 7, 2025
@ethanabrooks ethanabrooks force-pushed the eb/handle-thinking-only-requests branch 4 times, most recently from 7247ccd to 6139ac7 Compare August 7, 2025 19:33
@ethanabrooks ethanabrooks force-pushed the eb/handle-thinking-only-requests branch 2 times, most recently from 269ef33 to e40e7a5 Compare August 7, 2025 20:44
@ethanabrooks ethanabrooks force-pushed the eb/handle-thinking-only-requests branch from e40e7a5 to a06e745 Compare August 7, 2025 20:52
Copy link
Collaborator

@DouweM DouweM left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ethanabrooks Thanks Ethan, this fix makes sense. Can you please add a test with a simulated thinking-only response using FunctionModel?

@@ -482,6 +499,12 @@ async def _run_stream() -> AsyncIterator[_messages.HandleResponseEvent]:
self._next_node = await self._handle_text_response(ctx, last_texts)
return

# If there are no preceding model responses, we prompt the model to try again and provide actionable output.
breakpoint()
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please remove this breakpoint

if thinking_parts:
# Create the retry request using UserPromptPart for API compatibility
# We'll use a special content marker to detect this is a thinking retry
retry_part = _messages.UserPromptPart(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's use a RetryPromptPart as we do here:

m = _messages.RetryPromptPart(
content='Plain text responses are not permitted, please include your response in a tool call',
)

# Create the retry request using UserPromptPart for API compatibility
# We'll use a special content marker to detect this is a thinking retry
retry_part = _messages.UserPromptPart(
'Based on your thinking above, you MUST now provide '
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since we don't currently send back the thinking parts, the model won't know what this refers to. Can we simplify the message to be something more generic like "Responses without text or tool calls are not permitted." and see if that's enough to get the model to do better?

Note that wrapping it in a RetryPromptPart as suggested above already adds Fix the errors and try again. after this message:

def model_response(self) -> str:
"""Return a string message describing why the retry is requested."""
if isinstance(self.content, str):
if self.tool_name is None:
description = f'Validation feedback:\n{self.content}'
else:
description = self.content
else:
json_errors = error_details_ta.dump_json(self.content, exclude={'__all__': {'ctx'}}, indent=2)
description = f'{len(self.content)} validation errors: {json_errors.decode()}'
return f'{description}\n\nFix the errors and try again.'

actionable output alongside their thinking content.
"""
thinking_parts = [p for p in parts if isinstance(p, _messages.ThinkingPart)]
if thinking_parts:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can drop this check and always try to get the model to try again instead of raising a hard error.

@DouweM DouweM changed the title add handling for thinking-only requests (currently causes UnexpectedModelBehavior) Ask model to try again if it produced a response without text or tool call parts (e.g. only thinking) Aug 12, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

UnexpectedModelBehavior with Google Gemini thinking mode: "Received empty model response"
2 participants